﻿using System;
using System.Collections.Generic;

namespace theeg.GeometryWpf
{
    public class Node
    {
        internal enum LowAndHighCombination
        {
            LowXLowY,
            LowXHighY,
            HighXLowY,
            HighXHighY
        }

        //arbitrary data associated with this node
        //e.g. OtgNode, Transition
        internal IHilbertTreeNode StoredData;
        //the area controlled by the tree node
        internal ComputationalRectangle ControlledRegion;

        //the effective area spanned by the subtree
        private ComputationalRectangle _b = new ComputationalRectangle(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue);
        public ComputationalRectangle BoundingRegion
        {
            get
            {
                return _b;
            }
            set
            {
                _b = value;
                if (_b.L <= 0.0)
                {
                    int ulf = 0;
                    ulf++;
                }
            }
        }

        private System.Windows.Point? _representingPoint;
        internal System.Windows.Point RepresentingPoint
        {
            get
            {
                if (_representingPoint == null)
                {
                    ComputationalRectangle rect = StoredData.GetBoundingBox();
                    double X = 0, Y = 0;
                    switch (LowAndHigh)
                    {
                        case LowAndHighCombination.LowXLowY:
                            X = rect.L;
                            Y = rect.T;
                            break;
                        case LowAndHighCombination.LowXHighY:
                            X = rect.L;
                            Y = rect.B;
                            break;
                        case LowAndHighCombination.HighXLowY:
                            X = rect.R;
                            Y = rect.T;
                            break;
                        case LowAndHighCombination.HighXHighY:
                            X = rect.R;
                            Y = rect.B;
                            break;
                    }
                    _representingPoint = new System.Windows.Point(X, Y);
                }
                return _representingPoint.Value;
            }
        }

        //child nodes
        internal Node Left, Right;

        //the depth of the node inside the tree
        internal int Depth;

        //marker for deleted nodes
        //internal bool IsMarkedForDeletion;
        //the node represents, which point of the rectangle
        internal readonly LowAndHighCombination LowAndHigh;

        //hide default constructor
        private Node()
        {
        }

        //constructor
        internal Node(IHilbertTreeNode data, LowAndHighCombination lowAndHigh)
        {
            StoredData = data;
            LowAndHigh = lowAndHigh;
        }

        //node is leaf if it has no children
        internal bool IsLeaf()
        {
            return Left == null && Right == null;
        }

        //
        internal void Destroy()
        {
            if (Left != null)
                Left.Destroy();
            if (Right != null)
                Right.Destroy();
            StoredData = null;
            Left = null;
            Right = null;
            ControlledRegion = null;
        }
        //get all data represented by subtree
        internal void Report(ref HashSet<IHilbertTreeNode> ret)
        {
            /*if(!IsMarkedForDeletion)*/
            ret.Add(StoredData);
            if (Left != null)
                Left.Report(ref ret);
            if (Right != null)
                Right.Report(ref ret);
        }
        //insert a new node to the subtree
        internal void Insert(Node newNode)
        {
            System.Windows.Point pt = RepresentingPoint;
            System.Windows.Point ptNew = newNode.RepresentingPoint;
            if (Depth % HilbertTree.Dimensions == 0)
            {
                if (ptNew.X <= pt.X)
                {
                    if (Left == null)
                    {
                        Left = newNode;
                        newNode.Depth = Depth + 1;
                        newNode.ControlledRegion = new ComputationalRectangle(ControlledRegion.L, ControlledRegion.T, pt.X, ControlledRegion.B);
                        newNode.BoundingRegion = newNode.StoredData.GetBoundingBox();
                    }
                    else
                    {
                        Left.Insert(newNode);
                    }
                }
                else
                {
                    if (Right == null)
                    {
                        Right = newNode;
                        newNode.Depth = Depth + 1;
                        newNode.ControlledRegion = new ComputationalRectangle(pt.X, ControlledRegion.T, ControlledRegion.R, ControlledRegion.B);
                        newNode.BoundingRegion = newNode.StoredData.GetBoundingBox();
                    }
                    else
                    {
                        Right.Insert(newNode);
                    }
                }
            }
            else
            {
                if (ptNew.Y <= pt.Y)
                {
                    if (Left == null)
                    {
                        Left = newNode;
                        newNode.Depth = Depth + 1;
                        newNode.ControlledRegion = new ComputationalRectangle(ControlledRegion.L, ControlledRegion.T, ControlledRegion.R, pt.Y);
                        newNode.BoundingRegion = newNode.StoredData.GetBoundingBox();
                    }
                    else
                    {
                        Left.Insert(newNode);
                    }
                }
                else
                {
                    if (Right == null)
                    {
                        Right = newNode;
                        newNode.Depth = Depth + 1;
                        newNode.ControlledRegion = new ComputationalRectangle(ControlledRegion.L, pt.Y, ControlledRegion.R, ControlledRegion.B);
                        newNode.BoundingRegion = newNode.StoredData.GetBoundingBox();
                    }
                    else
                    {
                        Right.Insert(newNode);
                    }
                }
            }
            BoundingRegion = new ComputationalRectangle(Math.Min(BoundingRegion.L, newNode.BoundingRegion.L),
                Math.Min(BoundingRegion.T, newNode.BoundingRegion.T),
                Math.Max(BoundingRegion.R, newNode.BoundingRegion.R),
                Math.Max(BoundingRegion.B, newNode.BoundingRegion.B));
        }

        internal IHilbertTreeNode FindNearest(double xx, double yy, double range, int direction, int types)
        {
            DistanceNode dn = FindRegionNode(xx, yy, double.MaxValue, direction, types);
            if (dn == null)
                return null;
            if (Math.Abs(dn.Distance - double.MaxValue) < double.Epsilon || dn.Distance > range)
                return null;
            return dn.Node.StoredData;
        }

        /// <summary>
        /// internal method for finding the nearest node to a point
        /// </summary>
        /// <param name="xx">x coordinate of the point</param>
        /// <param name="yy">y coordinate of the point</param>
        /// <param name="currentMinDist">current minimum distance</param>
        /// <param name="direction">0=all, 1=north, 2=east, 4=south, 8=west, 16=not north, 32=not east, 64=not south, 128=not west</param>
        /// <param name="types">0-all, 1-nodes, 2-edges</param>
        /// <returns></returns>
        private DistanceNode FindRegionNode(double xx, double yy, double currentMinDist, int direction, int types)
        {
            double distthis;
            bool skipThis = true;
            //if(!IsMarkedForDeletion)
            {
                System.Windows.Point pt = RepresentingPoint;
                double x2 = Math.Pow((xx - pt.X), 2);
                double y2 = Math.Pow((yy - pt.Y), 2);
                distthis = Math.Sqrt(x2 + y2);
                //inclusive priority search
                if (direction == 0 ||
                    (pt.X < xx && (direction & 8) == 8) ||
                    (pt.Y > yy && (direction & 4) == 4) ||
                    (pt.X > xx && (direction & 2) == 2) ||
                    (pt.Y < yy && (direction & 1) == 1))
                {
                    skipThis = false;
                }
                //exclusive priority search beats inclusive search
                if (direction > 15 && (
                    (pt.X < xx && (direction & 128) == 128) ||
                    (pt.Y > yy && (direction & 64) == 64) ||
                    (pt.X > xx && (direction & 32) == 32) ||
                    (pt.Y < yy && (direction & 16) == 16)))
                {
                    skipThis = true;
                }

                if (types != 0 && (StoredData.Category & types) != StoredData.Category)
                {
                    skipThis = true;
                }
            }
            if (distthis < currentMinDist || ControlledRegion.Contains(xx, yy))
            {
                DistanceNode dnl = null;
                DistanceNode dnr = null;
                if (Left != null)
                    dnl = Left.FindRegionNode(xx, yy, skipThis ? currentMinDist : distthis, direction, types);
                if (Right != null)
                    dnr = Right.FindRegionNode(xx, yy, skipThis ? currentMinDist : distthis, direction, types);
                if (Math.Abs(distthis - double.MaxValue) < double.Epsilon)
                    return null;
                DistanceNode dnlow = new DistanceNode();
                dnlow.Distance = skipThis ? double.MaxValue : distthis;
                dnlow.Node = this;
                if (dnr != null && dnr.Distance < dnlow.Distance)
                {
                    dnlow = dnr;
                }
                if (dnl != null && dnl.Distance < dnlow.Distance)
                {
                    dnlow = dnl;
                }
                return dnlow;
            }
            return null;
        }

        internal void RecalculateRegion()
        {
            System.Windows.Point pt = RepresentingPoint;
            BoundingRegion = StoredData.GetBoundingBox();
            if (Left != null)
            {
                Left.Depth = Depth + 1;
                if (Depth % HilbertTree.Dimensions == 0)
                {
                    Left.ControlledRegion.R = pt.X;
                    Left.ControlledRegion.L = ControlledRegion.L;
                    Left.ControlledRegion.T = ControlledRegion.T;
                    Left.ControlledRegion.B = ControlledRegion.B;
                }
                else
                {
                    Left.ControlledRegion.B = pt.Y;
                    Left.ControlledRegion.L = ControlledRegion.L;
                    Left.ControlledRegion.T = ControlledRegion.T;
                    Left.ControlledRegion.R = ControlledRegion.R;
                }
                Left.RecalculateRegion();
                BoundingRegion = new ComputationalRectangle(Math.Min(BoundingRegion.L, Left.BoundingRegion.L),
                    Math.Min(BoundingRegion.T, Left.BoundingRegion.T),
                    Math.Max(BoundingRegion.R, Left.BoundingRegion.R),
                    Math.Max(BoundingRegion.B, Left.BoundingRegion.B));
            }
            if (Right != null)
            {
                Right.Depth = Depth + 1;
                if (Depth % HilbertTree.Dimensions == 0)
                {
                    Right.ControlledRegion.L = pt.X;
                    Right.ControlledRegion.R = ControlledRegion.R;
                    Right.ControlledRegion.T = ControlledRegion.T;
                    Right.ControlledRegion.B = ControlledRegion.B;
                }
                else
                {
                    Right.ControlledRegion.T = pt.Y;
                    Right.ControlledRegion.L = ControlledRegion.L;
                    Right.ControlledRegion.B = ControlledRegion.B;
                    Right.ControlledRegion.R = ControlledRegion.R;
                }
                Right.RecalculateRegion();
                BoundingRegion = new ComputationalRectangle(Math.Min(BoundingRegion.L, Right.BoundingRegion.L),
                    Math.Min(BoundingRegion.T, Right.BoundingRegion.T),
                    Math.Max(BoundingRegion.R, Right.BoundingRegion.R),
                    Math.Max(BoundingRegion.B, Right.BoundingRegion.B));
            }
        }
    }
}
